package com.zjuee.web.basic.captcha.service.impl;

import cn.hutool.core.util.IdUtil;
import com.zjuee.base.bean.response.CommonResultCode;
import com.zjuee.base.exception.BaseException;
import com.zjuee.web.basic.captcha.utils.CaptchaUtil;
import com.zjuee.web.basic.captcha.utils.FontUtil;
import com.zjuee.service.basic.captcha.vo.CaptchaVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 滑动验证码
 *
 * @author wu liang
 * @since 1.0
 */
@Slf4j
@Service(value = "slide")
public class SlideCaptchaServiceImpl extends AbstractCaptchaService {

    private static Logger logger = LoggerFactory.getLogger(SlideCaptchaServiceImpl.class);

    @Override
    public String captchaType() {
        return "slide";
    }

    /**
     * 获取验证码
     */
    @Override
    public CaptchaVo get() {
        HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 随机获取一张 滑动验证码的原图
        BufferedImage originalImage = CaptchaUtil.randomSlidingOriginal();
        if (null == originalImage)
            throw new BaseException(CommonResultCode.CONSUMER_ERR, "滑动底图未初始化成功，请检查路径");
        // 设置水印
        Graphics backgroundGraphics = originalImage.getGraphics();
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        //设置字体大小，float型
        Font font = FontUtil.getFont_CH(Font.BOLD, waterMarkFontSize);
        backgroundGraphics.setFont(font);
        backgroundGraphics.setColor(Color.white);
        backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - waterMarkFontSize + 7);

        // 随机获取一张抠图图片
        String cutImageName = CaptchaUtil.randomSlidingBlockName();
        BufferedImage cutImage = CaptchaUtil.getSlidingBlockByName(cutImageName);
        if (null == cutImage)
            throw new BaseException(CommonResultCode.CONSUMER_ERR, "滑动底图未初始化成功，请检查路径");
        CaptchaVo captcha = pictureTemplatesCut(originalImage, cutImage, cutImageName);
        if (captcha == null
                || StringUtils.isBlank(captcha.getCutImageBase64())
                || StringUtils.isBlank(captcha.getImageBase64())) {
            throw new BaseException(CommonResultCode.CONSUMER_ERR, "验证码生成失败！");
        }

        // 存入session
        CaptchaVo sessionBean = new CaptchaVo();
        sessionBean.setX(captcha.getX());
        sessionBean.setExpiresTimes(System.currentTimeMillis() + CaptchaUtil.SESSION_CAPT_EXPIRES_TIMES);
        request.getSession().setAttribute(CaptchaUtil.CAPTCHA_SESSION_TEMP_KEY, sessionBean);
        return captcha;
    }

    /**
     * 校验验证码
     */
    @Override
    public String check(CaptchaVo params) {
        HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Object attribute = request.getSession().getAttribute(CaptchaUtil.CAPTCHA_SESSION_TEMP_KEY);
        if (attribute == null)
            throw new BaseException(CommonResultCode.CAPTCHA_CHECK_ERR);
        //验证码只用一次，即刻失效
        request.getSession().removeAttribute(CaptchaUtil.CAPTCHA_SESSION_TEMP_KEY);
        if (!(attribute instanceof CaptchaVo))
            throw new BaseException(CommonResultCode.CAPTCHA_CHECK_ERR);

        CaptchaVo localCaptcha = (CaptchaVo) attribute;
        // 校验是否已失效
        if (System.currentTimeMillis() > localCaptcha.getExpiresTimes())
            throw new BaseException(CommonResultCode.CAPTCHA_CHECK_ERR, "验证码已失效，请重新获取");
        if (localCaptcha.getX() == null)
            throw new BaseException(CommonResultCode.CAPTCHA_CHECK_ERR);

        // 校验误差
        int resultX = Math.abs(localCaptcha.getX() - params.getX());
        if (resultX > slipOffset)
            throw new BaseException(CommonResultCode.CAPTCHA_CHECK_ERR);

        //校验成功，分发一个token
        String token = IdUtil.simpleUUID();
        CaptchaVo sessionBean = new CaptchaVo();
        sessionBean.setDefaultValue(token);
        sessionBean.setExpiresTimes(System.currentTimeMillis() + CaptchaUtil.SESSION_CAPT_EXPIRES_TIMES);
        request.getSession().setAttribute(CaptchaUtil.CAPTCHA_SESSION_KEY, sessionBean);
        return token;
    }

    /**
     * 根据模板切图
     */
    public CaptchaVo pictureTemplatesCut(BufferedImage originalImage, BufferedImage cutImage, String cutImageName) {
        try {
            int originalWidth = originalImage.getWidth();
            int originalHeight = originalImage.getHeight();
            int cutWidth = cutImage.getWidth();
            int cutHeight = cutImage.getHeight();

            //随机生成拼图坐标
            int x = generateJigsawPoint(originalWidth, originalHeight, cutWidth, cutHeight);

            //生成新的拼图图像
            BufferedImage newCutImage = new BufferedImage(cutWidth, cutHeight, cutImage.getType());
            Graphics2D graphics = newCutImage.createGraphics();

            //如果需要生成RGB格式，需要做如下配置,Transparency 设置透明
            newCutImage = graphics.getDeviceConfiguration().createCompatibleImage(cutWidth, cutHeight, Transparency.TRANSLUCENT);
            // 新建的图像根据模板颜色赋值,源图生成遮罩
            cutByTemplate(originalImage, cutImage, newCutImage, x, 0);

            int position;
            if (originalWidth - x - 5 > cutWidth * 2) {
                //在原扣图右边插入干扰图
                position = RandomUtils.nextInt(x + cutWidth + 5, originalWidth - cutWidth);
            } else {
                //在原扣图左边插入干扰图
                position = RandomUtils.nextInt(100, x - cutWidth - 5);
            }

            // 再插入两个不同的干扰图
            String s1 = CaptchaUtil.randomSlidingBlockName(cutImageName);
            interferenceByTemplate(
                    originalImage,
                    CaptchaUtil.getSlidingBlockByName(s1),
                    position, 0);

            // String s2 = CaptchaUtil.randomSlidingBlockName(cutImageName, s1);
            // Integer randomInt = RandomUtils.getRandomInt(cutWidth, 100 - cutWidth);
            // interferenceByTemplate(
            //         originalImage,
            //         CaptchaUtil.getSlidingBlockByName(s2),
            //         randomInt,
            //         0);

            // 设置“抗锯齿”的属性
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.setStroke(new BasicStroke(5, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
            graphics.drawImage(newCutImage, 0, 0, null);
            graphics.dispose();

            // 生成对象返回
            CaptchaVo bean = new CaptchaVo();
            bean.setImageWidth(originalWidth);
            bean.setImageHeight(originalHeight);
            bean.setCutImageWidth(cutWidth);
            bean.setImageBase64(CaptchaUtil.imageToDataUrl(originalImage));
            bean.setCutImageBase64(CaptchaUtil.imageToDataUrl(newCutImage));
            bean.setX(x);
            return bean;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 随机生成拼图坐标
     */
    private static int generateJigsawPoint(int originalWidth, int originalHeight, int cutWidth, int cutHeight) {
        Random random = new Random();
        int maxWidth = originalWidth - cutWidth;
        int x;
        if (maxWidth <= 0) {
            x = 5;
        } else {
            // 生成的x 距左 100 距右边 cutWidth
            x = random.nextInt(maxWidth - 100) + 100;
        }
        return x;
    }

    /**
     * @param oriImage      原图
     * @param templateImage 模板图
     * @param newImage      新抠出的小图
     * @param x             随机扣取坐标X
     * @param y             随机扣取坐标y
     * @throws Exception
     */
    private static void cutByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage newImage, int x, int y) {
        //临时数组遍历用于高斯模糊存周边像素值
        int[][] martrix = new int[3][3];
        int[] values = new int[9];

        int xLength = templateImage.getWidth();
        int yLength = templateImage.getHeight();
        // 模板图像宽度
        for (int i = 0; i < xLength; i++) {
            // 模板图片高度
            for (int j = 0; j < yLength; j++) {
                // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
                int rgb = templateImage.getRGB(i, j);
                if (rgb < 0) {
                    newImage.setRGB(i, j, oriImage.getRGB(x + i, y + j));

                    //抠图区域高斯模糊
                    readPixel(oriImage, x + i, y + j, values);
                    fillMatrix(martrix, values);
                    oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
                }

                //防止数组越界判断
                if (i == (xLength - 1) || j == (yLength - 1)) {
                    continue;
                }
                int rightRgb = templateImage.getRGB(i + 1, j);
                int downRgb = templateImage.getRGB(i, j + 1);
                //描边处理，,取带像素和无像素的界点，判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
                if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
                    newImage.setRGB(i, j, Color.white.getRGB());
                    oriImage.setRGB(x + i, y + j, Color.white.getRGB());
                }
            }
        }

    }


    /**
     * 干扰抠图处理
     *
     * @param oriImage      原图
     * @param templateImage 模板图
     * @param x             随机扣取坐标X
     * @param y             随机扣取坐标y
     * @throws Exception
     */
    private static void interferenceByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x, int y) {
        //临时数组遍历用于高斯模糊存周边像素值
        int[][] martrix = new int[3][3];
        int[] values = new int[9];

        int xLength = templateImage.getWidth();
        int yLength = templateImage.getHeight();
        // 模板图像宽度
        for (int i = 0; i < xLength; i++) {
            // 模板图片高度
            for (int j = 0; j < yLength; j++) {
                // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
                int rgb = templateImage.getRGB(i, j);
                if (rgb < 0) {
                    //抠图区域高斯模糊
                    readPixel(oriImage, x + i, y + j, values);
                    fillMatrix(martrix, values);
                    oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
                }
                //防止数组越界判断
                if (i == (xLength - 1) || j == (yLength - 1)) {
                    continue;
                }
                int rightRgb = templateImage.getRGB(i + 1, j);
                int downRgb = templateImage.getRGB(i, j + 1);
                //描边处理，,取带像素和无像素的界点，判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
                if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
                    oriImage.setRGB(x + i, y + j, Color.white.getRGB());
                }
            }
        }

    }

    private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
        int xStart = x - 1;
        int yStart = y - 1;
        int current = 0;
        for (int i = xStart; i < 3 + xStart; i++) {
            for (int j = yStart; j < 3 + yStart; j++) {
                int tx = i;
                if (tx < 0) {
                    tx = -tx;

                } else if (tx >= img.getWidth()) {
                    tx = x;
                }
                int ty = j;
                if (ty < 0) {
                    ty = -ty;
                } else if (ty >= img.getHeight()) {
                    ty = y;
                }
                pixels[current++] = img.getRGB(tx, ty);

            }
        }
    }

    private static void fillMatrix(int[][] matrix, int[] values) {
        int filled = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                x[j] = values[filled++];
            }
        }
    }

    private static int avgMatrix(int[][] matrix) {
        int r = 0;
        int g = 0;
        int b = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                if (j == 1) {
                    continue;
                }
                Color c = new Color(x[j]);
                r += c.getRed();
                g += c.getGreen();
                b += c.getBlue();
            }
        }
        return new Color(r / 8, g / 8, b / 8).getRGB();
    }


}
